package com.ymx.darling.bean.entity;

import com.ymx.darling.bean.annotation.Column;
import com.ymx.darling.bean.annotation.Entity;
import com.ymx.darling.bean.annotation.Id;
import com.ymx.darling.bean.entity.listener.SetListener;
import com.ymx.darling.exception.EntityException;
import com.ymx.darling.executor.mapping.relation.*;
import com.ymx.darling.executor.mapping.relation.annotation.RelationManyToMany;
import com.ymx.darling.executor.mapping.relation.annotation.RelationOneToMany;
import com.ymx.darling.executor.mapping.relation.annotation.RelationOneToOne;
import com.ymx.darling.logicdelete.LogicDeleteManager;
import com.ymx.darling.logicdelete.LogicDeleteProcessor;
import com.ymx.darling.mask.MaskTypeHandler;
import com.ymx.darling.type.DefaultTypeHandler;
import com.ymx.darling.util.ArrayUtil;
import com.ymx.darling.util.ObjectUtil;
import com.ymx.darling.util.ReflectUtil;
import com.ymx.darling.util.StringUtil;
import com.ymx.darling.bean.entity.id.IdType;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 保存BeanInfo实例的仓库
 * @author 爱Java的小于
 */
public class DefaultBeanInfoRepository implements BeanInfoRepository{
    private final Map<Class<?>, BeanInfo> beanInfoCache = new HashMap<>();
    private final Map<String, BeanInfo> tableBeanInfoCache = new HashMap<>();

    @Override
    public boolean hasTable(String tableName) {
        return tableBeanInfoCache.containsKey(tableName);
    }


    @Override
    public BeanInfo getBeanInfo(Class<?> entity) {
        return this.beanInfoCache.get(entity);
    }


    @Override
    public BeanInfo getTableBeanInfo(String tableName) {
        return this.tableBeanInfoCache.get(tableName);
    }


    @Override
    public void register(Class<?> entity) {
        // 提前创建一个半成品对象
        BeanInfoContent beanInfoContent = new BeanInfoContent();
        // 处理循环依赖
        beanInfoCache.put(entity, beanInfoContent);
        this.parseTableBean(entity, beanInfoContent);

        // 判断并注册Table的BeanInfo
        if (beanInfoContent.isTableEntity()) {
            String tableName = beanInfoContent.getTableName();
            if (tableBeanInfoCache.containsKey(tableName)) {
                BeanInfo beanInfo = tableBeanInfoCache.get(tableName);
                throw new EntityException(String.format("表%s对应的实体类型'%s'已经被注册,不允许实体类型'%s'再次指向该表", tableName, beanInfo.getModule().getName(), entity.getName()));
            }
            tableBeanInfoCache.put(tableName, beanInfoContent);
        }
    }


    /**
     * 解析一个javabean的基本信息
     *
     * @param module javabean
     * @param beanInfoContent beanInfoContent
     */
    private void parseTableBean(Class<?> module, BeanInfoContent beanInfoContent) {
        // 获取@Entity注解的表名
        String tableName = this.getDefaultTableName(module);
        beanInfoContent.setTableName(tableName);
        // 获取是否可填充
        boolean isFillable = this.getFillable(module);
        beanInfoContent.setFillable(isFillable);
        // 获取Schema模式名称
        String schemaName = this.getSchemaName(module);
        beanInfoContent.setSchemaName(schemaName);
        // 获取@Entity.entityDef的类的BeanInfo
        BeanInfo defBeanInfo = this.getEntityDefBeanInfo(module);
        beanInfoContent.setEntityDefBeanInfo(defBeanInfo);
        // 获取set侦听器
        List<SetListener> setListenerList = this.getSetListener(module);
        beanInfoContent.setOnSetListeners(setListenerList);

        FieldInfo versionFieldInfo = null; // 乐观锁版本号字段
        FieldInfo idFieldInfo = null; // id字段
        FieldInfo tenantIdFieldInfo = null; // 租户id字段
        FieldInfo logicDeleteFieldInfo = null; // 逻辑删除字段
        List<FieldInfo> queryFieldInfos = new ArrayList<>(); // 按顺序保存可以执行查询的字段集合
        List<FieldInfo> updateFieldInfos = new ArrayList<>(); // 按顺序保存可以执行更新字段集合
        Map<String, FieldInfo> fieldMap = new HashMap<>(); //根据属性名保存所有的field
        Map<String, FieldInfo> simpleColumnMap = new HashMap<>(); // 保存表字段与类属性对应关系
        List<RelationFieldInfo> relationFieldInfoList = new ArrayList<>(); //保存嵌套查询字段信息的集合

        for (Field field : getFieldAll(module)) {
            // field信息封装类
            FieldContent fieldInfo = new FieldContent();
            fieldInfo.setField(field);
            fieldInfo.setProperty(field.getName());
            field.setAccessible(true);
            fieldMap.put(field.getName(), fieldInfo);

            // 判断字段为嵌套类型或关联类型
            if (isNestedOrRelationField(field)) {
                RelationFieldInfo relationFieldInfo = this.getRelationFieldInfo(field);
                relationFieldInfoList.add(relationFieldInfo);

            // 判断字段为简单类型
            } else {
                String columnName = field.getName(); // 获取字段名称
                boolean ignoreFlag = false; // 是否是业务字段
                // 判断是否开启驼峰下划线转换
                if (this.isMapUnderSourceToCameCase(field)) {
                    columnName = StringUtil.mapUnderscoreToCamelCase(columnName);
                }
                // 判断包含@Colum注解
                if (field.isAnnotationPresent(Column.class)) {
                    Column column = field.getAnnotation(Column.class);
                    // 获取字段的业务状态
                    ignoreFlag = column.ignore();
                    // 判断字段是否重命名
                    if (StringUtil.isNoNull(column.value())) { //判断重新为当前的字段名赋值
                        columnName = column.value();
                    }
                    // 判断是否设置脱敏
                    if (StringUtil.isNoNull(column.maskType())) {
                        if (!String.class.equals(field.getType())) {
                            throw new EntityException(String.format("不能将脱敏字段%s的类型设置为%s,该字段类型只支持String", field.getName(), field.getType().getName()));
                        }
                        fieldInfo.setTypeHandler(new MaskTypeHandler(column.maskType()));
                    }
                    // 判断是否自定义类型处理器
                    if (column.typeHandler() != DefaultTypeHandler.class) {
                        try {
                            fieldInfo.setTypeHandler(column.typeHandler().newInstance());
                        } catch (InstantiationException | IllegalAccessException e) {
                            throw new EntityException(e, "类型转换器实例化失败:"+e.getMessage());
                        }
                    }
                    // 设置业务字段后一下功能无效
                    if (!ignoreFlag) {
                        // 字段为乐观锁字段
                        if (column.isVersion()) {
                            if (Objects.nonNull(versionFieldInfo)) {
                                throw new EntityException("不能出现两个标记为乐观锁的字段");
                            }
                            if (!this.checkOptimisticLockFieldType(field)) {
                                throw new EntityException(String.format("不能将乐观锁字段%s的类型设置为%s", field.getName(), field.getType().getName()));
                            }
                            versionFieldInfo = fieldInfo;
                        }
                        // 判断字段为逻辑删除字段
                        if (column.isLogicDelete()) {
                            if (Objects.nonNull(logicDeleteFieldInfo)) {
                                throw new EntityException("不能出现两个标记为逻辑删除的字段");
                            }
                            fieldInfo.setLogicDeleteProcessor(getLogicDeleteProcessor(field));
                            logicDeleteFieldInfo = fieldInfo;
                        }
                        // 判断字段为租户id字段
                        if (column.tenantId()) {
                            if (Objects.nonNull(tenantIdFieldInfo)) {
                                throw new EntityException("不能出现两个标记为租户id的字段");
                            }
                            tenantIdFieldInfo = fieldInfo;
                        }
                    }
                }
                // 判断框架不支持该字段的类型 抛出异常
                if (!ignoreFlag && Objects.isNull(fieldInfo.getTypeHandler()) && !ObjectUtil.isSimpleType(field.getType())) {
                    throw new EntityException(String.format("Darling不支持将未标示的字段'%s'的类型设置为'%s',如需请参考自定义类型处理器或将字段设置为关联字段", field.getName(), ObjectUtil.getSimpleName(field.getType())));
                }
                // 字段被@ID标注
                if (field.isAnnotationPresent(Id.class)) {
                    if (Objects.nonNull(idFieldInfo)) {
                        throw new EntityException("不能出现两个标识为id的字段");
                    }
                    idFieldInfo = fieldInfo;
                    this.setIdFieldInfo(field, fieldInfo);
                }
                // 统计非业务字段
                if (!ignoreFlag) {
                    updateFieldInfos.add(fieldInfo);
                    // 查询字段集合忽略逻辑删除字段
                    if (!fieldInfo.equals(logicDeleteFieldInfo)) {
                        queryFieldInfos.add(fieldInfo);
                    }
                }
                // field对应表字段的名称
                fieldInfo.setColumn(columnName);
                simpleColumnMap.put(columnName, fieldInfo);
            }
        }
        // 判断乐观锁字段必须同id字段同时使用
        if (versionFieldInfo != null && idFieldInfo == null) {
            throw new EntityException("实体类没有被@id注解的字段,因此无法正确配置乐观锁");
        }

        // 信息添加至 BeanInfo 中
        beanInfoContent.setModule(module);
        beanInfoContent.setFieldMap(fieldMap);
        beanInfoContent.setIdColumnInfo(idFieldInfo);
        beanInfoContent.setQueryColumn(queryFieldInfos);
        beanInfoContent.setUpdateColumn(updateFieldInfos);
        beanInfoContent.setSimpleColumnMap(simpleColumnMap);
        beanInfoContent.setTenantIdColumn(tenantIdFieldInfo);
        beanInfoContent.setVersionColumnInfo(versionFieldInfo);
        beanInfoContent.setLogicDeleteColumnInfo(logicDeleteFieldInfo);
        beanInfoContent.setRelationFieldInfoList(relationFieldInfoList);
    }


    /**
     * 获取实体类是否可填充
     * @param module  module
     * @return boolean
     */
    private boolean getFillable(Class<?> module) {
        return module.isAnnotationPresent(Entity.class) && module.getAnnotation(Entity.class).fillable();
    }


    /**
     * 返回field所属类是否设置了驼峰式转下划线
     * @param field field
     * @return boolean
     */
    private boolean isMapUnderSourceToCameCase(Field field) {
        Class<?> fieldDeclaringClass = field.getDeclaringClass();
        Entity entity = fieldDeclaringClass.getAnnotation(Entity.class);
        return Objects.nonNull(entity) && entity.camelToUnderscore();
    }


    /**
     * 返回类(包含父类)的所有字段
     * @param type type
     * @return list
     */
    private List<Field> getFieldAll(Class<?> type) {
        List<Class<?>> supperClassArray = ReflectUtil.getSupperClassArray(type);
        supperClassArray.add(0, type);
        List<Field> fieldList = new ArrayList<>();
        supperClassArray.forEach(tClass -> fieldList.addAll(ArrayUtil.asList(tClass.getDeclaredFields())));

        return fieldList;
    }


    /**
     * 返回逻辑删除处理器
     * @param field field
     * @return LogicDeleteProcessor
     */
    private LogicDeleteProcessor getLogicDeleteProcessor(Field field) {
        Class<?> fieldType = field.getType();
        LogicDeleteProcessor logicDeleteProcessor = LogicDeleteManager.getLogicDeleteProcessor(fieldType);
        if (Objects.isNull(logicDeleteProcessor)) {
            throw new EntityException(String.format("不能将逻辑字段'%s'的类型设置为:%s", field.getName(), fieldType.getName()));
        }
        return logicDeleteProcessor;
    }


    /**
     * 判断当前字段是否是一个嵌套类型
     * @param field field
     * @return boolean
     */
    private boolean isNestedOrRelationField(Field field) {
        if (field.isAnnotationPresent(RelationOneToOne.class) ||
                field.isAnnotationPresent(RelationOneToMany.class) ||
                field.isAnnotationPresent(RelationManyToMany.class)) {
            return true;
        } else {
            Class<?> type = field.getType();
            if (List.class.isAssignableFrom(type))  {
                type = ReflectUtil.getGenericTypeOfCollectionField(field);
            }
            return type != null && type.isAnnotationPresent(Entity.class);
        }
    }


    /**
     * 返回级联字段信息
     * @param field field
     * @return RelationFieldInfo
     */
    private RelationFieldInfo getRelationFieldInfo(Field field) {
        RelationFieldInfo relationFieldInfo = new RelationFieldInfo();
        relationFieldInfo.setField(field);
        // 处理一对多的情况
        if (field.isAnnotationPresent(RelationOneToMany.class)) {
            if (!List.class.isAssignableFrom(field.getType())) {
                throw new EntityException(String.format("被@RelationOneToMany标注的字段%s的类型必须为List或其实现类", field.getName()));
            }
            Class<?> genericType = ReflectUtil.getGenericTypeOfCollectionField(field);
            relationFieldInfo.setFieldType(genericType);
            relationFieldInfo.setAssociationType(RelationType.ONE_TO_MANY);
            RelationOneToManyLoader relationOneToManyLoader = new RelationOneToManyLoader(field.getAnnotation(RelationOneToMany.class));
            relationFieldInfo.setRelationLoader(relationOneToManyLoader);

        // 处理多对多的情况
        } else if (field.isAnnotationPresent(RelationManyToMany.class)) {
            if (!List.class.isAssignableFrom(field.getType())) {
                throw new EntityException(String.format("被@RelationOneToMany标注的字段%s的类型必须为List或其实现类", field.getName()));
            }
            Class<?> genericType = ReflectUtil.getGenericTypeOfCollectionField(field);
            relationFieldInfo.setFieldType(genericType);
            relationFieldInfo.setAssociationType(RelationType.MANY_TO_MANY);
            RelationManyToManyLoader relationManyToManyLoader = new RelationManyToManyLoader(field.getAnnotation(RelationManyToMany.class));
            relationFieldInfo.setRelationLoader(relationManyToManyLoader);

        // 处理一对一的情况
        } else if (field.isAnnotationPresent(RelationOneToOne.class)) {
            relationFieldInfo.setFieldType(field.getType());
            relationFieldInfo.setAssociationType(RelationType.ONE_TO_ONE);
            RelationOneToOneLoader relationOneToOneLoader = new RelationOneToOneLoader(field.getAnnotation(RelationOneToOne.class));
            relationFieldInfo.setRelationLoader(relationOneToOneLoader);

        // 处理不执行关联查询的关联字段
        } else {
            Class<?> fieldType = field.getType();
            if (List.class.isAssignableFrom(fieldType)) {
                Class<?> enttyClass = ReflectUtil.getGenericTypeOfCollectionField(field);
                relationFieldInfo.setFieldType(enttyClass);
                relationFieldInfo.setAssociationType(RelationType.ONE_TO_MANY);
            } else {
                relationFieldInfo.setFieldType(fieldType);
                relationFieldInfo.setAssociationType(RelationType.ONE_TO_ONE);
            }
        }

        // 判断字段的关联关系合法性
        RelationLoader relationLoader = relationFieldInfo.getRelationLoader();
        if (relationLoader != null) {
            Class<?> targetClass = relationLoader.getTargetClass();
            Class<?> fieldType = relationFieldInfo.getFieldType();
            if (targetClass == Void.class && !this.isScannerForEntity(fieldType)) {
                throw new EntityException(String.format("字段'%s'配置关联关系时发生错误:当字段类型为简单类型或集合中的类型为简单类型时,必须设置targetClass属性", field.getName()));
            }
            if (targetClass != Void.class && this.isScannerForEntity(fieldType)) {
                throw new EntityException(String.format("字段'%s'配置关联关系时发生错误:当字段类型为实体类型或集合中的类型为实体类型时,不能设置targetClass属性", field.getName()));
            }
        }

        return relationFieldInfo;
    }


    /**
     * 返回Set侦听器实例
     * @param module class
     */
    private List<SetListener> getSetListener(Class<?> module) {
        List<Class<?>> supperClassArray = ReflectUtil.getSupperClassArray(module);
        supperClassArray.add(0, module);
        List<SetListener> listeners = new ArrayList<>();
        // 获取类(包含父类型)的侦听器
        supperClassArray.forEach(type -> {
            Entity entity = type.getAnnotation(Entity.class);
            if (Objects.nonNull(entity)) {
                for (Class<?> listenerClass : entity.setListener()) {
                    if (!SetListener.class.isAssignableFrom(listenerClass)) {
                        throw new EntityException(String.format("'%s'不是'%s'的子类型", listenerClass.getName(), SetListener.class.getName()));
                    }
                    SetListener listener = ReflectUtil.newInstance(listenerClass);
                    listeners.add(listener);
                }
            }
        });
        return listeners;
    }


    /**
     * 返回应用的其他实体的BeanInfo
     * @param module module
     * @return BeanInfo 没有任何引用过则返回null
     */
    private BeanInfo getEntityDefBeanInfo(Class<?> module) {
        Entity entity = module.getAnnotation(Entity.class);
        Class<?> entityDef = entity.entityDef();
        if (!(entityDef == Void.class)) {
            BeanInfo entityDefBeanInfo = this.getBeanInfoIfAbsentRegister(entityDef);
            if (!entityDefBeanInfo.isTableEntity()) {
                throw new EntityException(String.format("@Entity注解中entityDef指定的类型%s不是表对应的实体类型", entityDef.getName()));
            } else {
                return entityDefBeanInfo;
            }
        }
        return null;
    }


    /**
     * 返回指定的BeanInfo如果不存在则重新注册
     * @param entity entity
     * @return BeanInfo
     */
    private BeanInfo getBeanInfoIfAbsentRegister(Class<?> entity) {
        if (!this.beanInfoCache.containsKey(entity)) {
            this.register(entity);
        }
        return this.beanInfoCache.get(entity);
    }


    /**
     * 返回实体对应表的名字
     * @param module module
     */
    private String getDefaultTableName(Class<?> module) {
        if (module == null) {
            throw new EntityException("不能将null解析为BeanInfo实例");
        }
        if (!module.isAnnotationPresent(Entity.class)) {
            throw new EntityException(String.format("类:'%s'缺少@Entity注解", module.getName()));
        }

        return module.getAnnotation(Entity.class).table();
    }

    /**
     * 获取实体对应的schema名称
     * @param module module
     * @return String
     */
    public String getSchemaName(Class<?> module) {
        return module.getAnnotation(Entity.class).schema();
    }


    /**
     * 检查当使用框架生成主键策略时 类型必须为long 或 String
     * @param field 主键Filed
     */
    private void setIdFieldInfo(Field field, FieldContent fieldContent) {
        Class<?> fieldType = field.getType();
        Id idAnnotation = field.getAnnotation(Id.class);
        IdType idType = idAnnotation.value();
        if (idType == IdType.ASSIGN) {
            if (fieldType != long.class && fieldType != Long.class && fieldType != String.class) {
                throw new EntityException("id字段类型错误:被IdType.GENERATE注解后字段"+field.getName()+"类型必须设置为long(Long)或者String");
            }
        }
        fieldContent.setIdType(idType);
        fieldContent.setKeySequence(idAnnotation.keySequence());
    }


    /**
     * 判断乐观锁字段类型是否符合
     * @param f field
     * @return true 符合 false 不符合
     */
    private boolean checkOptimisticLockFieldType(Field f) {
        Class<?> type = f.getType();
        return type == int.class || type == long.class
                || type == short.class || type == Integer.class
                || type == Long.class ||type == Short.class;
    }
}
